package com.jsy.common.util.gitIncrement;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.zip.*;

/**
 * Java增量打包工具类，用于生成Git版本差异的增量包,需要注意pom.xml文件的依赖升级和class文件不存在的文件。
 * 1导出 Git 版本差异：使用git diff命令获取两个版本之间的差异文件列表，并将这些文件复制到临时目录。
 * 2替换 Java 文件为 class 文件：遍历差异文件列表，将其中的 Java 文件替换为对应的 class 文件（如果存在）。这需要指定类文件目录和源文件目录。
 * 3生成版本日志：使用git log命令获取两个版本之间的提交历史，生成包含版本信息和变更记录的 readme.txt 文件。
 *
 * git log --oneline oldVersion newVersion 输出简短hash和提交标题
 * git log --format="%ad %an: %s" --date=short
 * 占位符	含义
 * %h	简短哈希值
 * %H	完整哈希值
 * %s	提交注释标题
 * %B	完整的提交注释
 * %an	作者名字
 * %ae	作者邮箱
 * %ad	提交日期
 * %cr	相对日期（例如 “2 天前”）
 * git log --format=%B 只输出日志
 */
@Deprecated
public class IncrementalPackagingUtils {

    /**
     * 生成增量包
     * @param gitRepoPath Git仓库路径
     * @param oldVersion 旧版本号
     * @param newVersion 新版本号
     * @param sourceDirPath 源代码目录
     * @param outputSourceZipPath 源代码ZIP包路径
     * @param outputReleaseZipPath 发版包ZIP包路径
     * @param classDirPath  项目类文件目录
     * @param classSeparator 类报名文件分隔符
     * @throws Exception
     */
    public static void generateIncrementalPackage(String gitRepoPath, String oldVersion, String newVersion,
                                                  String sourceDirPath, String outputSourceZipPath, String outputReleaseZipPath, String classDirPath, String classSeparator) throws Exception {
        // 1. 导出Git指定版本差异到系统临时目录
//        Path tempDir = Files.createTempDirectory("incremental");
        Path sourceDir = Paths.get(sourceDirPath);
        List<String> changedFiles = exportGitDiffToDirectory(gitRepoPath, oldVersion, newVersion, sourceDir.toString());

        // 2. 生成版本日志
        String readmeContent = generateVersionLog(gitRepoPath, oldVersion, newVersion);
        Path readmePath = sourceDir.resolve("readme.txt");
        Files.write(readmePath, readmeContent.getBytes(StandardCharsets.UTF_8));

        // 3. 创建源码ZIP包
        compressDirectory(sourceDir.toString(), outputSourceZipPath);

        // 4. 复制ZIP包并替换Java文件为class文件
        replaceJavaWithClass(outputSourceZipPath, outputReleaseZipPath, classDirPath, classSeparator);

        // 5. 创建发布ZIP包

        // 6. 清理临时文件
//        deleteDirectory(sourceDir.toFile());
//        new File(outputSourceZipPath).delete();

        System.out.println("增量包生成完成: " + outputReleaseZipPath);

    }

    /**
     * 导出Git版本差异到指定目录
     * @param gitRepoPath Git仓库路径
     * @param oldVersion 旧版本号
     * @param newVersion 新版本号
     * @param outputDir 输出目录
     * @return 变更的文件列表
     * @throws IOException 如果操作过程中发生I/O错误
     * @throws InterruptedException 如果操作被中断
     */
    private static List<String> exportGitDiffToDirectory(String gitRepoPath, String oldVersion, String newVersion, String outputDir)
            throws IOException, InterruptedException {
        List<String> changedFiles = getGitChangedFiles(gitRepoPath, oldVersion, newVersion);
        copyChangedFiles(gitRepoPath, changedFiles, outputDir);
        return changedFiles;
    }

    /**
     * 获取Git版本差异的文件列表
     * @param gitRepoPath Git仓库路径
     * @param oldVersion 旧版本号
     * @param newVersion 新版本号
     * @return 变更的文件列表
     * @throws IOException 如果操作过程中发生I/O错误
     * @throws InterruptedException 如果操作被中断
     */
    private static List<String> getGitChangedFiles(String gitRepoPath, String oldVersion, String newVersion)
            throws IOException, InterruptedException {
        List<String> result = new ArrayList<>();
        
        // 执行git diff命令获取变更文件列表
        ProcessBuilder pb = new ProcessBuilder(
                "git", "diff", "--name-only", oldVersion, newVersion);
        pb.directory(new File(gitRepoPath));
        Process process = pb.start();
        
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                result.add(line);
            }
        }
        
        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new IOException("Git命令执行失败，退出码: " + exitCode);
        }
        
        return result;
    }

    /**
     * 复制变更的文件到指定目录
     * @param gitRepoPath Git仓库路径
     * @param changedFiles 变更的文件列表
     * @param outputDir 输出目录
     * @throws IOException 如果操作过程中发生I/O错误
     */
    private static void copyChangedFiles(String gitRepoPath, List<String> changedFiles, String outputDir) throws IOException {
        for (String file : changedFiles) {
            File sourceFile = new File(gitRepoPath, file);
            File destFile = new File(outputDir, file);
            
            if (sourceFile.exists()) {
                // 创建目标目录
                destFile.getParentFile().mkdirs();
                
                // 复制文件
                Files.copy(sourceFile.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
            }
        }
    }

    /**
     * 生成版本日志
     * @param gitRepoPath Git仓库路径
     * @param oldVersion 旧版本号
     * @param newVersion 新版本号
     * @return 版本日志内容
     * @throws IOException 如果操作过程中发生I/O错误
     * @throws InterruptedException 如果操作被中断
     */
    private static String generateVersionLog(String gitRepoPath, String oldVersion, String newVersion)
            throws IOException, InterruptedException {
        StringBuilder log = new StringBuilder();
        log.append("版本变更日志\n");
        log.append("====================\n");
        log.append("生成时间: ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())).append("\n");
        log.append("旧版本: ").append(oldVersion).append("\n");
        log.append("新版本: ").append(newVersion).append("\n\n");
        log.append("变更记录:\n");
        log.append("--------------------\n");
        
        // 执行git log命令获取提交日志
//        ProcessBuilder pb = new ProcessBuilder("git", "log", "--oneline", oldVersion + ".." + newVersion);
        ProcessBuilder pb = new ProcessBuilder("git", "log", "--format=%B", oldVersion + ".." + newVersion);
        pb.directory(new File(gitRepoPath));
        Process process = pb.start();
        
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                log.append(line).append("\n");
            }
        }
        
        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new IOException("Git命令执行失败，退出码: " + exitCode);
        }
        
        return log.toString();
    }

    /**
     * 压缩目录到ZIP文件
     * @param sourceDir 源目录
     * @param zipPath 目标ZIP文件路径
     * @throws IOException 如果操作过程中发生I/O错误
     */
    private static void compressDirectory(String sourceDir, String zipPath) throws IOException {
        try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipPath))) {
            File directory = new File(sourceDir);
            compressDirectory(directory, directory.getName(), zipOut);
        }
    }

    /**
     * 递归压缩目录
     * @param directory 目录
     * @param parentPath 父路径
     * @param zipOut ZIP输出流
     * @throws IOException 如果操作过程中发生I/O错误
     */
    private static void compressDirectory(File directory, String parentPath, ZipOutputStream zipOut) throws IOException {
        File[] files = directory.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    compressDirectory(file, parentPath + "/" + file.getName(), zipOut);
                } else {
                    compressFile(file, parentPath + "/" + file.getName(), zipOut);
                }
            }
        }
    }

    /**
     * 压缩文件
     * @param file 文件
     * @param entryName 条目名称
     * @param zipOut ZIP输出流
     * @throws IOException 如果操作过程中发生I/O错误
     */
    private static void compressFile(File file, String entryName, ZipOutputStream zipOut) throws IOException {
        try (FileInputStream fis = new FileInputStream(file)) {
            ZipEntry zipEntry = new ZipEntry(entryName);
            zipOut.putNextEntry(zipEntry);
            
            byte[] bytes = new byte[1024];
            int length;
            while ((length = fis.read(bytes)) >= 0) {
                zipOut.write(bytes, 0, length);
            }
            zipOut.closeEntry();
        }
    }

    /**
     * 复制ZIP包并将Java文件替换为对应的class文件
     * @param sourceZipPath 源ZIP文件路径
     * @param destZipPath 目标ZIP文件路径
     * @param classDirPath 类文件目录路径
     * @param classSeparator
     * @throws IOException 如果操作过程中发生I/O错误
     */
    private static void replaceJavaWithClass(String sourceZipPath, String destZipPath, String classDirPath, String classSeparator)
            throws IOException {
        try (ZipInputStream zipIn = new ZipInputStream(new FileInputStream(sourceZipPath));
             ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(destZipPath))) {
            
            ZipEntry entry;
            while ((entry = zipIn.getNextEntry()) != null) {
                String entryName = entry.getName();
                // 1.如果是Java文件，尝试替换为class文件
                if (entryName.endsWith(".java")) {
                    String classFileName = entryName.replace(".java", ".class");
                    String[] split = classFileName.split(classSeparator);
                    String packageName = "";
                    String classFilePath = classFileName;
                    // 2.截取包名
                    if (split.length >=2){
                        packageName = split[split.length-1];
                        classFilePath = classDirPath + "/" + packageName;
                    }
                    File classFile = new File(classFilePath);
                    if (classFile.exists()) {
                        // 3.添加class文件到ZIP
                        addFileToZip(classFile, classFileName, zipOut);
                        // 4.添加同名编译内部类文件，查找当前目录下名字以classFile$开头的内部类
                        String fileName = classFile.getName();
                        String startName = fileName.replace(".class", "$");
                        File parentFile = classFile.getParentFile();
                        File[] internalClassFiles = parentFile.listFiles((dir, name) -> name.startsWith(startName));
                        if (internalClassFiles != null) {
                            for (File internalClassFile : internalClassFiles) {
                                // 5.处理内部类在压缩文件的路径
                                String internalClassFileName = classFileName.replace(fileName, internalClassFile.getName());
                                addFileToZip(internalClassFile, internalClassFileName, zipOut);
                            }
                        }
                   } else {
                        // class文件不存在，添加原Java文件
                        copyZipEntry(zipIn, entry, zipOut);
                        System.out.println("class文件不存在，需要手动添加添加原Java文件：" + classFileName);
                    }
                } else {
                    // 非Java文件，直接复制
                    copyZipEntry(zipIn, entry, zipOut);
                }
            }
        }
    }

    /**
     * 将文件添加到ZIP
     * @param file 文件
     * @param entryName 条目名称
     * @param zipOut ZIP输出流
     * @throws IOException 如果操作过程中发生I/O错误
     */
    private static void addFileToZip(File file, String entryName, ZipOutputStream zipOut) throws IOException {
        try (FileInputStream fis = new FileInputStream(file)) {
            ZipEntry zipEntry = new ZipEntry(entryName);
            zipOut.putNextEntry(zipEntry);
            
            byte[] bytes = new byte[1024];
            int length;
            while ((length = fis.read(bytes)) >= 0) {
                zipOut.write(bytes, 0, length);
            }
            zipOut.closeEntry();
        }
    }

    /**
     * 复制ZIP条目
     * @param zipIn ZIP输入流
     * @param entry ZIP条目
     * @param zipOut ZIP输出流
     * @throws IOException 如果操作过程中发生I/O错误
     */
    private static void copyZipEntry(ZipInputStream zipIn, ZipEntry entry, ZipOutputStream zipOut) throws IOException {
        ZipEntry newEntry = new ZipEntry(entry.getName());
        zipOut.putNextEntry(newEntry);
        
        byte[] bytes = new byte[1024];
        int length;
        while ((length = zipIn.read(bytes)) >= 0) {
            zipOut.write(bytes, 0, length);
        }
        zipOut.closeEntry();
    }

    /**
     * 删除目录及其所有内容
     * @param directory 目录
     * @return 删除成功返回true，否则返回false
     */
    private static boolean deleteDirectory(File directory) {
        if (directory.exists()) {
            File[] files = directory.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        deleteDirectory(file);
                    } else {
                        file.delete();
                    }
                }
            }
        }
        return directory.delete();
    }

    /**
     * 示例用法
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        try {
            // 配置参数
//            String gitRepoPath = "本地git项目路径";
//            String oldVersion = "旧版本提交ID";
//            String newVersion = "新版本提交ID";
//            String sourceDirPath = "git文件导出源文件目录路径";
//            String outputSourceZipPath = "输出ZIP源码增量包文件路径";
//            String outputReleaseZipPath = "输出ZIP发版增量包文件路径";
//            String classDirPath = "本地编译类文件目录路径";
//            String classSeparator = "源码压缩包包名分隔符，用于获取class文件包名";

            String gitRepoPath = "F:\\wizbank65\\exeed\\sh_wb_dev"; // Git仓库路径,本地项目路径
            String oldVersion = "df76605894861c0710da2dad2ee0ece5ccf64e1a"; // 旧版本提交ID
            String newVersion = "197c2f0cca4ad387f69c5695ba2829765e49775f"; // 新版本提交ID
            String sourceDirPath = "E:\\temp\\source"; // git文件导出源文件目录路径
            String outputSourceZipPath = "E:\\temp\\source.zip"; // 输出ZIP源码增量包文件路径
            String outputReleaseZipPath = "E:\\temp\\release.zip"; // 输出ZIP发版增量包文件路径
            String classDirPath = "F:\\wizbank65\\exeed\\sh_wb_dev\\core\\wizbank-web\\target\\WizBank\\WEB-INF\\classes"; // 本地编译类文件目录路径
            String classSeparator = "/src/main/java/"; //源码压缩包包名分隔符，用于获取class文件包名，maven项目固定为/src/main/java/

            // 一键生成增量包
            generateIncrementalPackage(gitRepoPath, oldVersion, newVersion,
                    sourceDirPath, outputSourceZipPath, outputReleaseZipPath,classDirPath,classSeparator);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}    